#include <cstdio>
#include <cmath>
#include <vector>
#include <glm/glm.hpp>
#include <glm/gtc/noise.hpp>

using namespace glm;

const int kImageWidth = 1280;
const int kImageHeight = 720;

inline float fbm(const vec3 &p) {
  return
    0.5000f * simplex(1.0f * p) +
    0.2500f * simplex(2.0f * p) +
    0.1250f * simplex(4.0f * p) +
    0.0625f * simplex(8.0f * p);
}
inline float Box(const vec3 &p, const vec3 &b, float r) {
  return length(max(abs(p) - b, 0.0f)) - r;
}
inline float Sphere(const vec3 &p, const vec3 &c, float r) {
  return length(p - c) - r;
}
float Map(const vec3 &p, int &matID) {
  float dist, minDist = 100.0f;
  dist = Box(p, vec3(1.5f, 1.0f, 1.5f), 0.03f);
  if (dist < minDist) {
    minDist = dist;
    matID = 1;
  }
  dist = Sphere(p, vec3(0.0f, 1.0f, 0.0f), 1.0f);
  if (dist < minDist) {
    minDist = dist;
    matID = 2;
  }
  dist = dot(p, vec3(0.0f, 1.0f, 0.0f));
  if (dist < minDist) {
    minDist = dist;
    matID = 3;
  }
  return minDist;
}
float Isect(const vec3 &ro, const vec3 &rd, int &matID) {
  for (float t = 0.0f; t < 40.0f;) {
    float d = Map(ro + rd*t, matID);
    if (d < 0.001f)
      return t;
    t += d;
  }
  matID = -1;
  return -1.0f;
}
float Shadow(const vec3 &ro, const vec3 &rd) {
  float r = 1.0f;
  int m;
  for (float t = 0.01f; t < 10.0f;) {
    float d = Map(ro + rd*t, m);
    if (d < 0.0005f)
      return 0.0f;
    r = min(r, 16.0f*d/t);
    t += d;
  }
  return r;
}
vec3 ComputeNormal(const vec3 &p) {
  int m;
  vec3 n;
  n.x = Map(vec3(p.x + 0.001f, p.y, p.z), m) - Map(vec3(p.x - 0.001f, p.y, p.z), m);
  n.y = Map(vec3(p.x, p.y + 0.001f, p.z), m) - Map(vec3(p.x, p.y - 0.001f, p.z), m);
  n.z = Map(vec3(p.x, p.y, p.z + 0.001f), m) - Map(vec3(p.x, p.y, p.z - 0.001f), m);
  return n;
}
vec3 ComputeBump(const vec3 &p) {
  vec3 b;
  float d = fbm(p);
  b.x = fbm(vec3(p.x + 0.001f, p.y, p.z)) - d;
  b.y = fbm(vec3(p.x, p.y + 0.001f, p.z)) - d;
  b.z = fbm(vec3(p.x, p.y, p.z + 0.001f)) - d;
  return b;
}

void ComputeColor(vec4 &out, float x, float y) {
  vec3 ro = vec3(2.0f, 3.0f, 3.0f);
  vec3 az = normalize(vec3(0.0f) - ro);
  vec3 ax = normalize(cross(vec3(0.0f, 1.0f, 0.0f), az));
  vec3 ay = normalize(cross(az, ax));
  vec3 rd = normalize(ax*x + ay*y + az*1.5f);

  vec3 c = vec3(0.0f);
  int matID;
  float d = Isect(ro, rd, matID);
  if (d > 0.0f) {
    vec3 p = ro + d*rd;
    vec3 n = ComputeNormal(p);
    /*
       if (matID == 2) {
       vec3 b = computeBump(8.0f * p);
       n = normalize(n + 0.5f * b);
       }
       else {*/
    n = normalize(n);
    //}
    //c = 0.5f*(1.0f+n);

    /*
    // AO
    float ao;
    float totao = 0.0f;
    float sca = 10.0f;
    for (int aoi = 0; aoi < 5; ++aoi) {
    float hr = 0.01f + 0.015f * static_cast<float>(aoi * aoi);
            vec3 aopos = n * hr + p;
            int m;
            float dd = map(aopos, m);
            ao = -(dd - hr);
            totao += ao*sca;
            sca *= 0.5f;
        }
        ao = 1.0f - clamp(totao, 0.0f, 1.0f);
        */

        vec3 rgb = vec3(1.0f);

        /*
        if (matID == 2) {
            float fb = fbm(8.0f * p);
            rgb = vec3(0.64f, 0.61f, 0.59f) + fb*vec3(0.21f, 0.19f, 0.19f);
        }
        */

        vec3 lightDir0 = normalize(vec3(10.0f, 8.0f, -6.0f) - p);
        vec3 lightDir1 = normalize(vec3(-12.0f, 19.0f, 6.0f) - p);

        c += 0.7f*max(0.4f + 0.6f*dot(n, lightDir0)*Shadow(p, lightDir0), 0.0f)*rgb;
        c += 0.3f*max(0.4f + 0.6f*dot(n, lightDir1)*Shadow(p, lightDir1), 0.0f)*rgb;
        //c *= ao;
    }

    out[0] = c.x;
    out[1] = c.y;
    out[2] = c.z;
    out[3] = 1.0f;
}

int main(int argc, char *argv[]) {
  std::vector<vec4> image(kImageWidth * kImageHeight);

  for (int j = 0; j < kImageHeight; ++j) {
    for (int i = 0; i < kImageWidth; ++i) {
      float x = (i + 0.5f) / kImageWidth;
      x = (x - 0.5f) * 2.0f * 1.777f;
      float y = (j + 0.5f) / kImageHeight;
      y = (y - 0.5f) * 2.0f;
      ComputeColor(image[j * kImageWidth + i], x, y);
    }
  }

  FILE *fp = fopen("test0.tga", "wb");
  const char tgaHeader[18] = { 0,0,2,0,0,0,0,0,0,0,0,0,kImageWidth & 0x00ff,(kImageWidth & 0xff00) >> 8,kImageHeight & 0x00ff,(kImageHeight & 0xff00) >> 8,32,0 };
  fwrite(tgaHeader, sizeof(tgaHeader), 1, fp);

  for (int i = 0; i < kImageWidth * kImageHeight; ++i) {
    char rgba[4];
    rgba[0] = static_cast<char>(255.0f * image[i][2]);
    rgba[1] = static_cast<char>(255.0f * image[i][1]);
    rgba[2] = static_cast<char>(255.0f * image[i][0]);
    rgba[3] = static_cast<char>(255.0f * image[i][3]);
    fwrite(rgba, sizeof(rgba), 1, fp);
  }

  fclose(fp);
  return 0;
}
